﻿//TraderStorageMonitor.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;
	using System.Threading;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Специальный класс, сохраняющий данные из шлюзов <see cref="Traders"/> во внешнее хранилище.
	/// </summary>
	public class TraderStorageMonitor : Disposable
	{
		private sealed class TraderList : BaseList<ITrader>
		{
			private readonly TraderStorageMonitor _monitor;

			public TraderList(TraderStorageMonitor monitor)
			{
				if (monitor == null)
					throw new ArgumentNullException("monitor");

				_monitor = monitor;
			}

			protected override bool OnAdding(ITrader item)
			{
				item.NewSecurities += OnNewSecurities;
				item.SecuritiesChanged += OnSecuritiesChanged;
				item.NewTrades += OnNewTrades;
				item.NewMyTrades += OnNewMyTrades;
				item.NewOrders += OnNewOrders;
				item.OrdersChanged += OnOrdersChanged;
				item.OrdersRegisterFailed += OnOrdersRegisterFailed;
				item.OrdersCancelFailed += OnOrdersCancelFailed;
				item.NewStopOrders += OnNewOrders;
				item.StopOrdersChanged += OnOrdersChanged;
				item.StopOrdersRegisterFailed += OnOrdersRegisterFailed;
				item.StopOrdersCancelFailed += OnOrdersCancelFailed;
				item.MarketDepthsChanged += OnMarketDepthsChanged;
				item.NewPortfolios += OnNewPortfolios;
				item.PortfoliosChanged += OnPortfoliosChanged;
				item.PositionsChanged += OnPositionsChanged;

				return base.OnAdding(item);
			}

			protected override bool OnRemoving(ITrader item)
			{
				item.NewSecurities -= OnNewSecurities;
				item.SecuritiesChanged -= OnSecuritiesChanged;
				item.NewTrades -= OnNewTrades;
				item.NewMyTrades -= OnNewMyTrades;
				item.NewOrders -= OnNewOrders;
				item.OrdersChanged -= OnOrdersChanged;
				item.OrdersRegisterFailed -= OnOrdersRegisterFailed;
				item.OrdersCancelFailed -= OnOrdersCancelFailed;
				item.NewStopOrders -= OnNewOrders;
				item.StopOrdersChanged -= OnOrdersChanged;
				item.StopOrdersRegisterFailed -= OnOrdersRegisterFailed;
				item.StopOrdersCancelFailed -= OnOrdersCancelFailed;
				item.MarketDepthsChanged -= OnMarketDepthsChanged;
				item.NewPortfolios -= OnNewPortfolios;
				item.PortfoliosChanged -= OnPortfoliosChanged;
				item.PositionsChanged -= OnPositionsChanged;

				return base.OnRemoving(item);
			}

			private void OnPositionsChanged(IEnumerable<Position> positions)
			{
				if (CanSave(TraderStorageFilters.PositionChanged))
					positions.ForEach(_monitor.SavePosition);
			}

			private void OnPortfoliosChanged(IEnumerable<Portfolio> portfolios)
			{
				if (CanSave(TraderStorageFilters.PortfolioChanged))
					portfolios.ForEach(_monitor.SavePortfolio);
			}

			private void OnNewPortfolios(IEnumerable<Portfolio> portfolios)
			{
				if (CanSave(TraderStorageFilters.NewPortfolio))
					portfolios.ForEach(_monitor.SavePortfolio);
			}

			private void OnMarketDepthsChanged(IEnumerable<MarketDepth> depths)
			{
				if (CanSave(TraderStorageFilters.DepthsChanged))
					depths.ForEach(_monitor.SaveQuotes);
			}

			private void OnOrdersChanged(IEnumerable<Order> orders)
			{
				if (CanSave(TraderStorageFilters.OrderChanged))
					orders.ForEach(_monitor.SaveOrder);
			}

			private void OnOrdersRegisterFailed(IEnumerable<OrderFail> fails)
			{
				if (CanSave(TraderStorageFilters.RegisterOrderFailed))
					fails.ForEach(_monitor.SaveRegisterOrderFail);
			}

			private void OnOrdersCancelFailed(IEnumerable<OrderFail> fails)
			{
				if (CanSave(TraderStorageFilters.RegisterCancelFailed))
					fails.ForEach(_monitor.SaveCancelOrderFail);
			}

			private void OnNewOrders(IEnumerable<Order> orders)
			{
				if (CanSave(TraderStorageFilters.NewOrder))
					orders.ForEach(_monitor.SaveOrder);
			}

			private void OnNewTrades(IEnumerable<Trade> trades)
			{
				if (CanSave(TraderStorageFilters.NewTrade))
					trades.ForEach(_monitor.SaveTrade);
			}

			private void OnSecuritiesChanged(IEnumerable<Security> securities)
			{
				if (CanSave(TraderStorageFilters.SecurityChanged))
					securities.ForEach(_monitor.SaveSecurity);
			}

			private void OnNewSecurities(IEnumerable<Security> securities)
			{
				if (CanSave(TraderStorageFilters.NewSecurity))
					securities.ForEach(_monitor.SaveSecurity);
			}

			private void OnNewMyTrades(IEnumerable<MyTrade> trades)
			{
				if (CanSave(TraderStorageFilters.NewMyTrade))
					trades.ForEach(_monitor.SaveMyTrade);
			}

			private bool CanSave(TraderStorageFilters filter)
			{
				return _monitor.Filter.Contains(filter);
			}
		}

		private readonly MarketDataBuffer<MarketDepth> _depthBuffer = new MarketDataBuffer<MarketDepth>();
		private readonly MarketDataBuffer<Trade> _tradeBuffer = new MarketDataBuffer<Trade>();

		private DateTime _prevMarketDepthTime;
		private readonly Timer _timer;

		/// <summary>
		/// Создать <see cref="TraderStorageMonitor"/>.
		/// </summary>
		/// <param name="storageRegistry">Интерфейс, описывающий внешнее хранилище.</param>
		public TraderStorageMonitor(IStorageRegistry storageRegistry)
		{
			if (storageRegistry == null)
				throw new ArgumentNullException("storageRegistry");

			StorageRegistry = storageRegistry;

			Filter =
				TraderStorageFilters.NewSecurity |
				TraderStorageFilters.SecurityChanged |
				TraderStorageFilters.NewPortfolio |
				TraderStorageFilters.PortfolioChanged |
				TraderStorageFilters.PositionChanged |
				TraderStorageFilters.NewTrade |
				TraderStorageFilters.NewMyTrade |
				TraderStorageFilters.DepthsChanged |
				TraderStorageFilters.NewOrder |
				TraderStorageFilters.OrderChanged;

			Traders = new TraderList(this);

			_timer = ThreadingHelper.Timer(() =>
			{
				while (!IsDisposed)
				{
					_depthBuffer.Get().ForEach(p => StorageRegistry.GetMarketDepthStorage(p.Key).Save(p.Value));
					_tradeBuffer.Get().ForEach(p => StorageRegistry.GetTradeStorage(p.Key).Save(p.Value));

					TimeSpan.FromSeconds(1).Sleep();
				}
			}).Interval(TimeSpan.FromSeconds(1));
		}

		/// <summary>
		/// Интерфейс, описывающий внешнее хранилище.
		/// </summary>
		public IStorageRegistry StorageRegistry { get; private set; }

		/// <summary>
		/// Шлюзы, данные которых необходимо сохранять.
		/// </summary>
		public ICollection<ITrader> Traders { get; private set; }

		/// <summary>
		/// Приостановлен ли процесс сохранения поступающих данных.
		/// </summary>
		public bool IsSuspended { get; set; }

		/// <summary>
		/// Фильтры данных, которые необходимо сохранять.
		/// По-умолчанию, выбраны все фильтры.
		/// Для установления нового фильтра необходимо использовать следующий вид записи:
		/// <code>
		/// // сохранять только новые инструменты, заявки и мои сделки
		/// Mode = TraderStorageFilters.NewSecurity | TraderStorageFilters.NewOrder | TraderStorageFilters.NewMyTrade;
		/// </code>
		/// </summary>
		public TraderStorageFilters Filter { get; set; }

		/// <summary>
		/// Дискретный шаг во времени, определяющий, как часто надо сохранять изменения в стакане.
		/// По умолчанию значение равно <see cref="TimeSpan.Zero"/>, что означает непрерывное сохранение изменений в стакане.
		/// </summary>
		public TimeSpan MarketDepthTimeStep { get; set; }

		/// <summary>
		/// Принудительно очистить кэш.
		/// </summary>
		public void Flush()
		{
			_depthBuffer.Get().ForEach(p => StorageRegistry.GetMarketDepthStorage(p.Key).Save(p.Value));
			_tradeBuffer.Get().ForEach(p => StorageRegistry.GetTradeStorage(p.Key).Save(p.Value));
		}

		/// <summary>
		/// Сохранить информацию по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, информацию которого необходимо сохранить.</param>
		protected virtual void SaveSecurity(Security security)
		{
			StorageRegistry.Securities.Save(security);
		}

		/// <summary>
		/// Сохранить информацию по заявке.
		/// </summary>
		/// <param name="order">Заявка, информацию которой необходимо сохранить.</param>
		protected virtual void SaveOrder(Order order)
		{
			StorageRegistry.Orders.Save(order);
		}

		/// <summary>
		/// Сохранить информацию об ошибке регистрации заявки.
		/// </summary>
		/// <param name="fail">Описание ошибки, которая произошла при регистрации заявки.</param>
		protected virtual void SaveRegisterOrderFail(OrderFail fail)
		{
			StorageRegistry.OrderFails.Save(fail);
		}

		/// <summary>
		/// Сохранить информацию об ошибке снятия заявки.
		/// </summary>
		/// <param name="fail">Описание ошибки, которая произошла при снятии заявки.</param>
		protected virtual void SaveCancelOrderFail(OrderFail fail)
		{
			StorageRegistry.OrderFails.Save(fail);
		}

		/// <summary>
		/// Сохранить информацию по моей сделке.
		/// </summary>
		/// <param name="trade">Моя сделка, информацию которой необходимо сохранить.</param>
		protected virtual void SaveMyTrade(MyTrade trade)
		{
			StorageRegistry.MyTrades.Save(trade);
		}

		/// <summary>
		/// Сохранить информацию по сделке.
		/// </summary>
		/// <param name="trade">Сделка, информацию которой необходимо сохранить.</param>
		protected virtual void SaveTrade(Trade trade)
		{
			_tradeBuffer.Add(trade.Security, trade);
		}

		/// <summary>
		/// Сохранить информацию по стакану.
		/// </summary>
		/// <param name="depth">Стакан, информацию которого необходимо сохранить.</param>
		protected virtual void SaveQuotes(MarketDepth depth)
		{
			if ((DateTime.Now - _prevMarketDepthTime) < MarketDepthTimeStep)
				return;

			_prevMarketDepthTime = DateTime.Now;

			_depthBuffer.Add(depth.Security, depth.Clone());
		}

		/// <summary>
		/// Сохранить информацию по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, информацию которого необходимо сохранить.</param>
		protected virtual void SavePortfolio(Portfolio portfolio)
		{
			StorageRegistry.Portfolios.Save(portfolio);
		}

		/// <summary>
		/// Сохранить информацию по позиции.
		/// </summary>
		/// <param name="position">Позиция, информацию которой необходимо сохранить.</param>
		protected virtual void SavePosition(Position position)
		{
			StorageRegistry.Positions.Save(position);
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			_timer.Dispose();
			Traders.Clear();
			Flush();
			base.DisposeManaged();
		}
	}
}
